package com.qianxun.framework.common.util;

import lombok.Getter;

import java.util.ArrayList;
import java.util.List;

/**
 * 数字编码生成工具
 * 数字编码: 即编码中的每一个字符都是且只是0~9中的某一个数字组成；
 *  示例： 001002003004000
 * 编码用途：该编码的设计是为了标识一组由上下层级关系组成的数据，从一个编码即可知道它所能包含的所有子编码，从一个字编码亦能知道它的所有父编码，
 * 从而从编码关系可以直接看出数据的关联关系，便于查找到需要的数据；且该编码适用于数据库级联表，可通过编码快速查找指定数据，节省数据查询时间，提升查询性能
 * @author: huwei
 * @date: 2019/10/18 15:01
 * @version: 1.0.0
 */
public class NumberCodeGenerator {
    private static final int DEFAULT_ALL_LENGTH = 30;
    private static final int DEFAULT_NODE_LENGTH = 3;
    /**
     * 补码
     */
    private static final String COMPLEMENT = "0";
    /**
     * 编码总长度，与节点长度联合使用，限定编码的层级数量；注意总长度与节点的余值为0
     * 示例：总长：30 ，节点长度：3 ，即层级数为： 30 / 3 = 10
     */
    @Getter
    private int allLength;
    /**
     * 单个层级节点的长度，通过节点长度可明确知道每一层可表示多少个数据类型，再结合层级数，即可推导出该编码所能表示的最大数据量
     * 示例：节点长度：3 ，每一个编码字符由0~9的数组构成，所以每层最大表示数为 999 ，若层级数为： 10 ，则最多可表示数据量为： 10个999的乘积，非常大
     */
    @Getter
    private int nodeLength;

    /**
     * 构建指定长度和节点长度的数据编码工具
     * @param allLength 编码总长度，与节点长度联合使用，限定编码的层级数量；注意总长度与节点的余值为0 示例：总长：30 ，节点长度：3 ，即层级数为： 30 / 3 = 10
     * @param nodeLength 单个层级节点的长度，通过节点长度可明确知道每一层可表示多少个数据类型，再结合层级数，即可推导出该编码所能表示的最大数据量 ；示例：节点长度：3 ，每一个编码字符由0~9的数组构成，所以每层最大表示数为 999 ，若层级数为： 10 ，则最多可表示数据量为： 999 * 10 = 9990
     */
    private NumberCodeGenerator(int allLength , int nodeLength){
        this.allLength = allLength;
        this.nodeLength = nodeLength;
    }
    /**
     * 构建一个总长为30，节点为3的编码生成器
     * @return
     */
    public static NumberCodeGenerator build(){
        return build(DEFAULT_ALL_LENGTH ,DEFAULT_NODE_LENGTH);
    }
    /**
     * 构建指定编码长度的生成器；给定总长除以节点长度余数必须为0
     * @param allLength 编码总长
     * @param nodeLength 编码每一个节点长度
     * @return
     */
    public static NumberCodeGenerator build(int allLength , int nodeLength){
        if(allLength < nodeLength || allLength % nodeLength != 0 || nodeLength <= 0){
            throw new RuntimeException("总长度与节点长度错误");
        }
        return new NumberCodeGenerator(allLength ,nodeLength);
    }
    /**
     * 生成首个编码
     * @return
     */
    public String first(){
        return StringUtil.addSuffix(startNode() ,COMPLEMENT ,allLength);
    }
    /**
     * 生成给定编码的下一个编码
     * @param code 编码，为空或空串时，返回当前编码规范的首个编码
     * @return 下一个编码
     * @throws Exception 当编码不符合当前编码规则，即：与指定总长度不一直，或则编码不是数字编码
     */
    public String next(String code) throws Exception{
        if(ObjectUtil.isBlank(code)){
            return first();
        }
        //编码合法性校验
        valid(code);
        int lastNodeIndex = lastNodeIndex(code);
        String nextNode = nextNode(lastNode(code));
        return new StringBuffer(code).delete(lastNodeIndex ,nodeLength + lastNodeIndex).insert(lastNodeIndex ,nextNode).toString();
    }
    /**
     * 生成给定编码的下一层级编码
     * @param code 编码 ，为空或空串时，返回当前编码规范的首个编码
     * @return 下一层级编码
     * @throws Exception 当编码不符合当前编码规则，即：与指定总长度不一直，或则编码不是数字编码
     */
    public String child(String code) throws Exception{
        if(ObjectUtil.isBlank(code)){
            return first();
        }
        //编码合法性校验
        valid(code);
        int childNodeIndex = lastNodeIndex(code) + nodeLength;
        if(childNodeIndex == allLength){
            throw new Exception("当前编码已经是最后的子节点编码，无法在生成下一层级编码");
        }
        return new StringBuffer(code).delete(childNodeIndex ,nodeLength + childNodeIndex).insert(childNodeIndex ,startNode()).toString();
    }
    /**
     * 获取指定编码的所有父编码的原始编码
     * @param code 编码
     * @return
     */
    public List<String> parentNode(String code){
        List<String> list = new ArrayList<>();
        if(!ObjectUtil.isBlank(code) && original(code).length() > nodeLength){
            String original = original(code);
            for(int i = 0 ,n = original.length() / nodeLength ;i < n -1 ;++i){
                list.add(original.substring(0 ,i * nodeLength + nodeLength));
            }
        }
        return list;
    }
    /**
     * 获取指定编码的所有父编码
     * @param code 编码
     * @return
     */
    public List<String> parentCode(String code){
        List<String> list = parentNode(code);
        List<String> res = new ArrayList<>(list.size());
        list.forEach(x->{
            res.add(StringUtil.addSuffix(x ,COMPLEMENT ,allLength));
        });
        return res;
    }
    /**
     * 获取指定编码的原始编码，即去掉后缀补码的数据
     * @param code 指定编码
     * @return
     */
    public String original(String code){
        StringBuffer sb = new StringBuffer(StringUtil.removeSuffix(code ,COMPLEMENT));
        //为100这样的编码补码
        for(int i = 0 ,len = nodeLength - (sb.length() % nodeLength) ;len < 3 && i < len ;++i){
            sb.append(COMPLEMENT);
        }
        return sb.toString();
    }
    /**
     * 获取当前编码规则下的最大编码数量
     * @return
     */
    public long codeNumber(){
        long codeNumber = maxNodeValue();
        for(int i = 1 ; i < nodeNumber();++i) {
            codeNumber = codeNumber * maxNodeValue();
        }
        return codeNumber < 0 ? Long.MAX_VALUE : codeNumber;
    }
    /**
     * 获取当前编码规则下的层级数量
     * @return
     */
    public int nodeNumber(){
        return allLength / nodeLength;
    }
    /**
     * 校验编码是否符合当前编码规则
     * @param code 编码
     */
    private void valid(String code) throws Exception {
        if(code.length() != allLength){
            String msg = StringUtil.concat("编码不符合当前编码规则，总长度应为：" ,allLength ,"示例：" ,first());
            throw new Exception(msg);
        }else{
            //检索当前编码是否为数字编码
            try{
                int maxLongValueLength = String.valueOf(Long.MAX_VALUE).length();
                int parseTimer = code.length() / maxLongValueLength + (code.length() % maxLongValueLength != 0 ? 1 : 0);
                for(int i = 0 ;i < parseTimer ;++i){
                    int start = i * maxLongValueLength;
                    int end = i * maxLongValueLength + maxLongValueLength > code.length() ? code .length() : i * maxLongValueLength + maxLongValueLength;
                    Long.parseLong(code.substring(start,end));
                }
            }catch (NumberFormatException e){
                throw new Exception("当前编码不是数字编码" ,e);
            }
        }
    }
    /**
     * 获取给定编码最后一个节点的起始下标
     * @param code 编码
     * @return
     */
    private int lastNodeIndex(String code){
        String tmp = original(code);
        return ObjectUtil.isBlank(tmp) ? 0 : tmp.length() - nodeLength;
    }
    /**
     * 获取最后一个节点
     * @param code 编码
     * @return
     */
    private String lastNode(String code){
        int start = lastNodeIndex(code);
        return code.substring(start , start + nodeLength);
    }
    /**
     * 获取给定节点的下一个节点
     * @param node 指定节点，为空和空串时返回首个节点编码
     * @return 当存在可用节点时，返回对应的节点编码，反之为null
     */
    private String nextNode(String node){
        if(ObjectUtil.isBlank(node)){
            return startNode();
        }
        long nodeValue = Long.parseLong(node) + 1;
        return nodeValue > maxNodeValue() ? null : StringUtil.addPrefix(String.valueOf(nodeValue) ,COMPLEMENT ,nodeLength);
    }
    /**
     * 获取编码的每一层节点所能表达的最大值
     * @return
     */
    private int maxNodeValue(){
        StringBuffer stringBuffer = new StringBuffer();
        for(int i = 0 ;i < nodeLength ;++i){
            stringBuffer.append(9);
        }
        return Integer.parseInt(stringBuffer.toString());
    }
    /**
     * 首个节点编码
     * @return
     */
    private String startNode(){
        return StringUtil.addPrefix(String.valueOf(1) ,COMPLEMENT ,nodeLength);
    }
}
